#include <sys/param.h>
#include <sndfile.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "pcm.h"
#include "converters.h"

/* libsndfile utility functions for accessing memory buffers */
static sf_count_t
mbuf_get_filelen(void *user_data) {
	struct memory_io_ctx *ctx = user_data;

	return (ctx->stream_size);
}

static sf_count_t
mbuf_seek(sf_count_t offset, int whence, void *user_data) {
	struct memory_io_ctx *ctx = user_data;

//	printf("seek: %lld %d\n", offset, whence);
	switch(whence) {
	case SEEK_SET:
		ctx->stream_offset = offset;
	break;
	case SEEK_CUR:
		ctx->stream_offset += offset;
	break;
	case SEEK_END:
		ctx->stream_offset = ctx->stream_size - offset;
	break;
	default:
		return -1;
	}

	return (ctx->stream_offset);
}

static sf_count_t
mbuf_read(void *ptr, sf_count_t count, void *user_data) {
	struct memory_io_ctx *ctx = user_data;
	
	//printf("read %p %lld\n", ptr, count);
	size_t length = MIN(ctx->stream_size - ctx->stream_offset, count);
	memcpy(ptr, ctx->stream_contents + ctx->stream_offset, length);
	ctx->stream_offset += length;

	return (length);
}

static sf_count_t
mbuf_write(const void *ptr, sf_count_t count, void *user_data) {
	struct memory_io_ctx *ctx = user_data;

	size_t length = MIN(ctx->stream_size - ctx->stream_offset, count);
	memcpy(ctx->stream_contents + ctx->stream_offset, ptr, length);
	ctx->stream_offset += length;

	if (count > length)
		ctx->flags |= MCTX_OUT_OF_BOUNDS;
	return (length);
}

static sf_count_t 
mbuf_tell(void *user_data) {
	struct memory_io_ctx *ctx = user_data;
	return (ctx->stream_offset);
}

void
_init_ctx_sf_virtual_io(SF_VIRTUAL_IO *vio)
{
	memset(vio, 0, sizeof(*vio));
	vio->get_filelen = mbuf_get_filelen;
	vio->seek = mbuf_seek;
	vio->read = mbuf_read;
	vio->write = mbuf_write;
	vio->tell = mbuf_tell;
}

/* generic libsndfile decoder */

static struct mediafs_decoder_ctx *
sndfile_decoder_open(char const *stream_contents, size_t stream_size)
{
	SF_VIRTUAL_IO vio;
	struct sndfile_decoder_ctx *result;

	result = calloc(1, sizeof(struct sndfile_decoder_ctx));

	result->stream_contents = result->mem_ctx.stream_contents = (char *)stream_contents;
	result->stream_size = result->mem_ctx.stream_size = stream_size;

	_init_ctx_sf_virtual_io(&vio);
	result->sndfile = sf_open_virtual(&vio, SFM_READ, &result->sf_info, &result->mem_ctx);

	result->ctx_size = sizeof(*result);
	return (struct mediafs_decoder_ctx *)result;
}

static void
sndfile_decoder_close(struct mediafs_decoder_ctx *ctx)
{
	struct sndfile_decoder_ctx *sc;

	sc = (struct sndfile_decoder_ctx *)ctx;
	sf_close(sc->sndfile);
	free(ctx);
}

static int
sndfile_decoder_get_stat(struct mediafs_decoder_ctx *ctx, struct mediafs_decoder_stat *stat)
{
	struct sndfile_decoder_ctx *sndfile_ctx;

	sndfile_ctx = (struct sndfile_decoder_ctx *)ctx;
	memset(stat, 0, sizeof(*stat));
	stat->len_samples = sndfile_ctx->sf_info.frames;
	stat->samples_per_sec = sndfile_ctx->sf_info.samplerate;
	stat->bits_per_sample = 16;
	stat->num_channels = sndfile_ctx->sf_info.channels;

	return 0;
}

static int
sndfile_decoder_decode(struct mediafs_decoder_ctx *ctx, uint64_t samples_from, uint64_t samples_duration, char *buffer) {
	struct sndfile_decoder_ctx *sndfile_ctx;
	sf_count_t result;

	sndfile_ctx = (struct sndfile_decoder_ctx *)ctx;
	sf_seek(sndfile_ctx->sndfile, samples_from, SEEK_SET);
	result = sf_readf_short(sndfile_ctx->sndfile, (short *)buffer, samples_duration); //TODO: hack with casting char * to short * - possible alignment problems
	
	return 0;
}

/* generic libsndfile encoder */

#define INITIAL_HEADER_BUFFER_SIZE 4096

static struct mediafs_encoder_ctx *
sndfile_encoder_open(struct mediafs_encoder_params *params) {
	short dummy_frames[32];
	SF_VIRTUAL_IO vio;
	SF_INFO sf_info;
	struct sndfile_encoder_params *sndfile_params;
	struct sndfile_encoder_ctx *result;
	size_t frame_size;
	int res;

	sndfile_params  = calloc(1, params->params_size);
	memcpy(sndfile_params, params, params->params_size);

	result = calloc(1, sizeof(*result));
	result->ctx_size = sizeof(*result);
	result->params = sndfile_params;

	memset(&sf_info, 0, sizeof(sf_info));
	sf_info.frames = sndfile_params->source_stat.len_samples;
	sf_info.samplerate = sndfile_params->source_stat.samples_per_sec;
	sf_info.channels = sndfile_params->source_stat.num_channels;
	sf_info.format = sndfile_params->format;
	_init_ctx_sf_virtual_io(&vio);
	result->sndfile = sf_open_virtual(&vio, SFM_RDWR, &sf_info, &result->mem_ctx);

	result->header_buffer_size = INITIAL_HEADER_BUFFER_SIZE;
	result->header_buffer = calloc(1, result->header_buffer_size);

	result->mem_ctx.stream_contents = result->header_buffer;
	result->mem_ctx.stream_size = result->header_buffer_size;
	res = sf_command(result->sndfile, SFC_UPDATE_HEADER_NOW, NULL, 0);
	assert(res == 0);
	result->header_size = result->mem_ctx.stream_offset;

	sf_writef_short(result->sndfile, dummy_frames, 1);
	frame_size = result->mem_ctx.stream_offset - result->header_size;

	result->mem_ctx.stream_size = result->header_size + frame_size * params->source_stat.len_samples;
	res = sf_command(result->sndfile, SFC_UPDATE_HEADER_NOW, NULL, 0);
	assert(res == 0);

	memset(&result->mem_ctx, 0, sizeof(result->mem_ctx));
	return ((struct mediafs_encoder_ctx *)result);
}

static void
sndfile_encoder_close(struct mediafs_encoder_ctx *ctx) {
	struct sndfile_encoder_ctx *sndfile_ctx;

	sndfile_ctx = (struct sndfile_encoder_ctx *)ctx;
	sf_close(sndfile_ctx->sndfile);
	free(sndfile_ctx->header_buffer);
	free(sndfile_ctx->params);
	free(sndfile_ctx);
}

static int
pcm_encoder_get_stat(struct mediafs_encoder_ctx *ctx, struct mediafs_encoder_stat *stat)
{
	struct sndfile_encoder_ctx *sndfile_ctx;

	sndfile_ctx = (struct sndfile_encoder_ctx *)ctx;
	memset(stat, 0, sizeof(*stat));
	stat->frames_number = sndfile_ctx->params->source_stat.len_samples;
	stat->samples_per_frame = 1;
	stat->frame_size = (sndfile_ctx->params->source_stat.bits_per_sample / 8) * sndfile_ctx->params->source_stat.num_channels;
	return 0;
}

static int
sndfile_encoder_write_header(struct mediafs_encoder_ctx *ctx, char *buffer, size_t *buffer_size)
{
	struct sndfile_encoder_ctx *sndfile_ctx;

	sndfile_ctx = (struct sndfile_encoder_ctx *)ctx;
	if (buffer == NULL) {
		*buffer_size = sndfile_ctx->header_size;
		return 0;
	}

	if (*buffer_size < sndfile_ctx->header_size) {
		return 1;
	}
	memcpy(buffer, sndfile_ctx->header_buffer, sndfile_ctx->header_size);
	*buffer_size = sndfile_ctx->header_size;
	
	return 0;
}

static int
sndfile_encoder_encode(struct mediafs_encoder_ctx *ctx, uint64_t frames_offset, uint64_t frames_duration, char *src_buffer,
        char *dest_buffer, size_t *dest_buffer_size)
{
	struct sndfile_encoder_ctx *sndfile_ctx;
	sf_count_t res;

	sndfile_ctx = (struct sndfile_encoder_ctx *)ctx;

	sndfile_ctx->mem_ctx.stream_contents = dest_buffer;
	sndfile_ctx->mem_ctx.stream_size = *dest_buffer_size;
	sndfile_ctx->mem_ctx.stream_offset = 0;

	res = sf_writef_short(sndfile_ctx->sndfile, (short *)src_buffer, frames_duration);
	*dest_buffer_size = sndfile_ctx->mem_ctx.stream_offset;
	return (res == frames_duration ? 0 : 1);
}

static struct mediafs_decoder mediafs_pcm_decoder = {
	.name = "pcm",
	.ctx_open_func = sndfile_decoder_open,
	.ctx_close_func = sndfile_decoder_close,
	.ctx_get_stat_func = sndfile_decoder_get_stat,
	.ctx_decode_func = sndfile_decoder_decode
};

static struct mediafs_encoder mediafs_pcm_encoder = {
	.name = "pcm",
	.ctx_open_func = sndfile_encoder_open,
	.ctx_close_func = sndfile_encoder_close,
	.ctx_get_stat_func = pcm_encoder_get_stat,
	.ctx_write_header_func = sndfile_encoder_write_header,
	.ctx_encode_func = sndfile_encoder_encode
};

struct mediafs_decoder *
get_mediafs_pcm_decoder()
{
	return &mediafs_pcm_decoder;
}

void
fill_mediafs_pcm_encoder_params(struct pcm_encoder_params *params, struct mediafs_decoder_stat *decoder_stat)
{
	memset(params, 0, sizeof(*params));
	params->params_size = sizeof(*params);
	memcpy(&params->source_stat, decoder_stat, sizeof(*decoder_stat));
	params->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 | SF_ENDIAN_FILE;
}

struct mediafs_encoder *
get_mediafs_pcm_encoder()
{
	return &mediafs_pcm_encoder;
}

__weak_reference(get_mediafs_pcm_decoder, get_mediafs_flac_decoder);
